package com.example.jwt.framework.config;

import com.example.jwt.framework.security.filter.JwtAuthenticationTokenFilter;
import com.example.jwt.framework.security.handle.AuthenticationEntryPointImpl;
import com.example.jwt.framework.security.handle.CustomExpiredSessionStrategyImpl;
import com.example.jwt.framework.security.handle.LogoutSuccessHandlerImpl;
import com.example.jwt.framework.security.handle.DynamicAuthorizationManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;

/**
 * @version 1.0.0
 * @className: SecurityConfig
 * @description: SpringSecurity 5.7.x新用法配置
 * @author: LiJunYi
 * @create: 2022/7/26 8:43
 */
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig
{
    /**
     * 自定义用户登录处理逻辑
     */
    private final UserDetailsService userDetailsService;

    /**
     * 注销成功后处理器
     */
    private final LogoutSuccessHandlerImpl logoutSuccessHandler;

    /**
     * 无权限处理器
     */
    private final AuthenticationEntryPointImpl unauthorizedHandler;

    /**
     * 并发登录控制处理器
     */
    private final CustomExpiredSessionStrategyImpl expiredSessionStrategy;

    /**
     * jwt身份验证令牌过滤器
     */
    private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    private final DynamicAuthorizationManager dynamicAuthorizationManager;

    /**
     * 安全配置
     * 构造函数注入
     *
     * @param userDetailsService           用户详细信息服务
     * @param logoutSuccessHandler         退出处理器
     * @param unauthorizedHandler          无权限处理器
     * @param expiredSessionStrategy       并发登录控制处理器
     * @param jwtAuthenticationTokenFilter JWT登录过滤器
     */
    @Autowired
    public SecurityConfig(UserDetailsService userDetailsService, LogoutSuccessHandlerImpl logoutSuccessHandler,
                          AuthenticationEntryPointImpl unauthorizedHandler,
                          CustomExpiredSessionStrategyImpl expiredSessionStrategy,
                          JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter, DynamicAuthorizationManager dynamicAuthorizationManager) {
        this.jwtAuthenticationTokenFilter = jwtAuthenticationTokenFilter;
        this.userDetailsService = userDetailsService;
        this.logoutSuccessHandler = logoutSuccessHandler;
        this.unauthorizedHandler = unauthorizedHandler;
        this.expiredSessionStrategy = expiredSessionStrategy;
        this.dynamicAuthorizationManager = dynamicAuthorizationManager;
    }


    /**
     * 身份验证实现
     */
    @Bean
    public AuthenticationManager authenticationManager()
    {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
        return new ProviderManager(daoAuthenticationProvider);
    }

    /**
     * 过滤器链
     *
     * @param http http
     * @return {@link SecurityFilterChain}
     * @throws Exception 异常
     */
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception
    {
        return http
                // CSRF禁用，因为不使用session
                .csrf(AbstractHttpConfigurer::disable)
                // 禁用HTTP响应标头
                .headers((headersCustomizer) ->
                                headersCustomizer.cacheControl(HeadersConfigurer.CacheControlConfig::disable)
                                        .frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
                // 认证失败处理类
                .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
                // 基于token，所以不需要session
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                // 接口配置
                .authorizeHttpRequests((authorizeRequests) ->
                                authorizeRequests.requestMatchers("/userLogin","/noPermission").permitAll()
                                        // .anyRequest().authenticated())
                                        .anyRequest().access(dynamicAuthorizationManager)) // 动态权限校验逻辑
                // 登出设置
                .logout(logout -> logout.logoutUrl("/logout")
                        .logoutSuccessHandler(logoutSuccessHandler))
                // 添加jwt 过滤器
                .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
                // 跨域设置
                .cors((cors) ->
                        cors.configurationSource(corsConfigurationSource()))
                .build();
    }

    /**
     * 配置跨源访问(CORS)
     * 官方文档：https://docs.spring.io/spring-security/reference/servlet/integrations/cors.html
     * @return {@link CorsConfigurationSource}
     */
    @Bean
    CorsConfigurationSource corsConfigurationSource()
    {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:8080/","https://example.com"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    /**
     * 配置加密方式
     *
     * @return {@link PasswordEncoder}
     */
    @Bean
    public PasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
